package hotpatchimpl

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/opensourceways/server-common-lib/utils"
	"github.com/sirupsen/logrus"

	"cvevulner/cve-ddd/domain"
	"cvevulner/util"
)

const (
	hotPatchIssue = "https://gitee.com/api/v5/repos/openeuler/hotpatch_meta/issues?" +
		"access_token=%s&state=closed&labels=%s&sort=created&direction=desc&page=%d&per_page=20&created_at=%s"
	endpoint             = "https://api-cve.openeuler.org"
	defaultClientTimeout = 3
	defaultMaxId         = 1000
)

var (
	regexpCve     = regexp.MustCompile(`<id>(?s:(.*?))</id>`)
	regexpRPM     = regexp.MustCompile(`热补丁路径[:：](?s:(.*?))热补丁信息[:：]`)
	regexpMeta    = regexp.MustCompile(`热补丁元数据[:：](?s:(.*?))热补丁路径[:：]`)
	regexpType    = regexp.MustCompile(`问题类别[:：](?s:(.*?))热补丁元数据[:：]`)
	regexpInfo    = regexp.MustCompile(`热补丁信息[:：](?s:(.*?))$`)
	regepHotPatch = regexp.MustCompile(`openEuler-HotPatchSA-(\d{4})-(\d{4,5})`)
)

func NewHotPatchImpl(log *logrus.Entry) *hotPatchImpl {
	return &hotPatchImpl{
		log: log,
	}
}

type hotPatchImpl struct {
	log *logrus.Entry
}

func (impl hotPatchImpl) GetIssueInfo() ([]domain.HotPatchIssue, error) {
	issues, err := impl.getIssues()
	if err != nil {
		return nil, fmt.Errorf("get hot patch issue error: %w", err)
	}

	var patches []domain.HotPatchIssue
	for _, v := range issues {
		pat, err1 := impl.toPatchIssue(v.Body)
		if err1 != nil {
			impl.log.Errorf("issue number %s toPatchIssue error: %v", v.Number, err1)
			continue
		}
		pat.HotIssueNum = v.Number

		patches = append(patches, pat)
	}

	return patches, nil
}

type Issue struct {
	Number string `json:"number"`
	Body   string `json:"body"`
}

func (impl hotPatchImpl) getIssues() ([]Issue, error) {
	var page = 1
	var issues []Issue
	token := beego.AppConfig.String("gitee::git_token")
	cli := utils.NewHttpClient(3)
	// query the data within a month
	filterCreatedTime := time.Now().AddDate(0, -1, 0).Format("20060102T1504015-")

	for {
		url := fmt.Sprintf(hotPatchIssue, token, "hotpatch", page, filterCreatedTime)
		req, err := http.NewRequest(
			http.MethodGet, url, nil,
		)
		if err != nil {
			return nil, err
		}

		res, _, err := cli.Download(req)
		if err != nil {
			return nil, err
		}

		var t []Issue
		if err = json.Unmarshal(res, &t); err != nil {
			return nil, err
		}

		if len(t) == 0 {
			break
		}

		issues = append(issues, t...)

		page++
	}

	return issues, nil
}

func (impl hotPatchImpl) toPatchIssue(body string) (v domain.HotPatchIssue, err error) {
	t := regexpType.FindAllStringSubmatch(body, -1)
	if len(t) == 0 {
		return v, errors.New("parse type failed")
	}
	v.Type = strings.TrimSpace(t[0][1])

	meta := regexpMeta.FindAllStringSubmatch(body, -1)
	if len(meta) == 0 {
		return v, errors.New("parse metadata failed")
	}
	split := strings.Split(meta[0][1], "/")
	v.Branch = split[len(split)-4]
	v.Component = split[len(split)-3]

	p := regexpRPM.FindAllStringSubmatch(body, -1)
	if len(p) == 0 {
		return v, errors.New("parse rpm failed")
	}
	v.PatchUrl = strings.Split(strings.TrimSpace(p[0][1]), "\n")

	info := regexpInfo.FindAllStringSubmatch(body, -1)
	if len(info) == 0 {
		return v, errors.New("parse info failed")
	}
	var bys []byte
	for _, s := range strings.Split(strings.TrimSpace(info[0][1]), "\n") {
		bys, err = util.HTTPGetCom(strings.TrimSpace(s))
		if err != nil {
			continue
		}

		if cve := regexpCve.FindAllStringSubmatch(string(bys), -1); len(cve) > 0 {
			v.CveNum = strings.Split(cve[0][1], ",")
			break
		}
	}

	if len(v.CveNum) == 0 {
		return v, errors.New("parse cve num failed")
	}

	return
}

type maxIdResult struct {
	Code   int    `json:"code"`
	Result string `json:"result"`
	Msg    string `json:"msg"`
}

func (impl hotPatchImpl) MaxHotPatchID() (maxId int, err error) {
	cli := utils.NewHttpClient(defaultClientTimeout)

	url := fmt.Sprintf("%s/cve-security-notice-server/securitynotice/getMaxHotPatchId", endpoint)

	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return
	}

	r, _, err := cli.Download(request)
	if err != nil {
		return
	}

	var res maxIdResult
	if err = json.Unmarshal(r, &res); err != nil {
		return
	}

	if res.Code != 0 {
		err = errors.New(res.Msg)

		return
	}

	// init id
	if res.Result == "" {
		return defaultMaxId, nil
	}

	match := regepHotPatch.FindAllStringSubmatch(res.Result, -1)
	if len(match) == 0 {
		err = errors.New("invalid bulletin id")

		return
	}

	// reset id to 1000 at new year
	if match[0][1] != strconv.Itoa(time.Now().Year()) {
		return defaultMaxId, nil
	}

	return strconv.Atoi(match[0][2])
}

type cveProductPackageResult struct {
	Code   int                 `json:"code"`
	Result []cveProductPackage `json:"result"`
	Msg    string              `json:"msg"`
}

type cveProductPackage struct {
	Id               int64     `json:"id"`
	CveId            string    `json:"cveId"`
	PackageName      string    `json:"packageName"`
	ProductName      string    `json:"productName"`
	Status           string    `json:"status"`
	Reason           string    `json:"reason"`
	SecurityNoticeNo string    `json:"securityNoticeNo"`
	ReleaseTime      string    `json:"releaseTime"`
	Updateime        string    `json:"updateTime"`
	CreateTime       time.Time `json:"createTime"`
}

func (p cveProductPackage) IsHotPatch() bool {
	return strings.Contains(p.SecurityNoticeNo, domain.BulletinTypeHotPatch)
}

func (impl hotPatchImpl) IsPublished(cveId, packageName, branch string) (bool, error) {
	cli := utils.NewHttpClient(defaultClientTimeout)

	url := fmt.Sprintf("%s/cve-security-notice-server/cvedatabase/getCVEProductPackageList?cveId=%s&packageName=%s",
		endpoint, cveId, packageName)

	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return false, err
	}

	r, _, err := cli.Download(request)
	if err != nil {
		return false, err
	}

	var res cveProductPackageResult
	if err = json.Unmarshal(r, &res); err != nil {
		return false, err
	}

	if res.Code != 0 {
		err = errors.New(res.Msg)

		return false, err
	}

	for _, v := range res.Result {
		if v.IsHotPatch() && v.ProductName == branch {
			return true, nil
		}
	}

	return false, nil
}
